﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DistributedGraph.EIGStop
{
    /// <summary>
    /// EIGStop spezifischer Knoten
    /// </summary>
    public class Node : DistributedGraph.Node
    {
        #region Typen
        public delegate void MsgEvent(Node sender, Node receiver, Link link, Message message);
        public delegate void SimpleEvent(Node node);
        #endregion Typen

        #region Attribute
        private TreeNode tree;
        private int round;
        private Dictionary<Node, List<TreeNode>> nodesThisRound;
        private bool roundDone;
        private bool isFinished = false;
        private bool isConsensReached = false;
        private HashSet<int> decisionResultSet = new HashSet<int>();
        private bool isDecisionCorrect = true;
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Gibt die Baumstruktur des Knotens an.
        /// </summary>
        public TreeNode Tree
        {
            get { return tree; }
        }

        /// <summary>
        /// Gibt und setzt (falls der Algorithmus nicht begonnen hat) den Wert des Knotens an.
        /// </summary>
        public int Value
        {
            get { return tree.Value; }
            set
            {
                if (IsStarted) throw new Exception("Der Wert kann nach Start des Algorithmus nicht mehr verändert werden!");
                this.tree.Value = value;
            }
        }

        /// <summary>
        /// Gibt true wenn der Algorithmus angefangen hat.
        /// </summary>
        public bool IsStarted { get { return roundDone || isFinished || nodesThisRound.Count > 0 || FailStops; } }

        /// <summary>
        /// Gibt true wenn der Algorithmus abgeschlossen ist.
        /// </summary>
        public bool IsFinished { get { return isFinished; } }

        /// <summary>
        /// Gibt die TreeKnoten-Senderknoten-Paare zurück die diese Runde bereits empfangen wurden.
        /// </summary>
        public Dictionary<Node, List<TreeNode>> NodesThisRound { get { return nodesThisRound; } }

        /// <summary>
        /// EIG spezifische Sende Event. (für Logging)
        /// </summary>
        public event MsgEvent MessageSent;

        /// <summary>
        /// EIG spezifisches Empfangs Event. (für Logging)
        /// </summary>
        public event MsgEvent MessageReceived;

        /// <summary>
        /// EIG spezifisches Empfangs Event. (für Logging)
        /// Wird im Fall einer erwarteten Nachricht die durch FailStop ausbleibt gerufen.
        /// </summary>
        public event MsgEvent NoMessageReceived;

        /// <summary>
        /// Wird geworfen wenn die Runde beendet wurde.
        /// </summary>
        public event SimpleEvent RoundFinished;

        /// <summary>
        /// Wird geworfen wenn die Entscheidungsregel angewendet wurde.
        /// </summary>
        public event SimpleEvent DecisionFinished;

        /// <summary>
        /// Gibt an ob Konsens erreicht wurde.
        /// </summary>
        public bool IsConsensReached { get { return isConsensReached; } }

        /// <summary>
        /// Gibt die Entscheidungsmenge zurück.
        /// </summary>
        public HashSet<int> DecisionResultSet { get { return decisionResultSet; } }

        /// <summary>
        /// Gibt an ob genug Runden durchlaufen wurden, um festzustellen ob die Entscheidung korrekt ist.
        /// </summary>
        public bool IsDecisionCorrect { get { return isDecisionCorrect; } }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisert den Knoten.
        /// </summary>
        public Node()
            : base()
        {
            TreeNode newTreeNode = new TreeNode(new int[] { }, 0);
            tree = newTreeNode;

            round = 0;
            roundDone = false;
            nodesThisRound = new Dictionary<Node, List<TreeNode>>();
        }

        /// <summary>
        /// EIGStop spezifische Sendemethode.
        /// </summary>
        /// <param name="link">Der Link über den kommuniziert wird.</param>
        /// <param name="message">Die Nachricht die kommuniziert wird.</param>
        public override void Send(Link link, IMessage message)
        {
            if (message is Message && MessageSent != null) MessageSent(this, (Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), link, (Message)message);
            base.Send(link, message);
        }

        /// <summary>
        /// EIGStop spezifische Broadcastmethode, wobei die Runde für diesen Knoten nach dem Broadcast beendet ist.
        /// </summary>
        /// <param name="message">Eine Dummynachricht.</param>
        public override void Broadcast(IMessage message)
        {
            if (roundDone) throw new Exception("Diese Runde ist bereits abgeschlossen.");
            List<TreeNode> tnl = new List<TreeNode>();
            List<TreeNode> leaves = tree.GetLeaves();

            if (FailStops)
            {
                // Dafür dass die anderen Knoten wissen, dass dieser Ausgefallen ist,
                // da die Angaben der Zeit nicht über Zeit sondern über Benutzerticks gesteuert sind.
                foreach (Link l in Links) ((Node)(l.SourceNode == this ? l.TargetNode : l.SourceNode)).Solve(l);
            }
            else
            {
                // Regulärer Broadcast.
                foreach (TreeNode tn in leaves) if (!tn.Label.Contains(Id)) tnl.Add(tn.Clone());
                base.Broadcast(new Message(tnl));

                // Simulieren dass man sich selbst anfunken kann
                if (MessageSent != null) MessageSent(this, this, null, new Message(tnl));
                if (MessageReceived != null) MessageReceived(this, this, null, new Message(tnl));
                nodesThisRound.Add(this, tnl);
            }
            roundDone = true;
        }

        /// <summary>
        /// EIGStop spezifische Empfangsmethode
        /// </summary>
        /// <param name="link"></param>
        /// <param name="message"></param>
        internal override void Receive(Link link, IMessage message)
        {
            base.Receive(link, message);
            if (!FailStops)
            {
                nodesThisRound.Add((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), ((Message)message).TreeNodes);
                if (message is Message && MessageReceived != null) MessageReceived((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), this, link, (Message)message);
            }
            isConsensReached = false;
            isFinished = false;
        }

        /// <summary>
        /// Teilt mit dass der Tick verstrichen ist und keine Nachricht angekommen ist.
        /// </summary>
        /// <param name="link">Der Link über den keine Übertragung stattgefunden hat.</param>
        internal void Solve(Link link)
        {
            if (!FailStops)
            {
                Node originNode = (Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode);
                List<TreeNode> leaves = tree.GetLeaves();
                List<TreeNode> tnl = new List<TreeNode>();

                // Man kann die tnl zwar übergeben, wenn man jedoch davon ausgeht,
                // dass der knoten der aufruft ausgefallen ist, dann muss dieser 
                // hier das reproduzieren können.
                foreach (TreeNode tn in leaves)
                {
                    if (!tn.Label.Contains(originNode.Id))      // Muss hier nochmal geprüft werden, da ja gilt i nicht element x
                    {
                        tnl.Add(new TreeNode(tn.Label));
                    }
                }
                nodesThisRound.Add(originNode, tnl);

                if (NoMessageReceived != null) NoMessageReceived((Node)(link.SourceNode == this ? link.TargetNode : link.SourceNode), this, link, new Message(tnl));
                isConsensReached = false;
                isFinished = false;
            }
        }

        /// <summary>
        /// Schaltet zur nächsten Runde weiter.
        /// </summary>
        /// <param name="f">Fehlerhafte Prozesse</param>
        public void FinishRound(int f)
        {
            if (roundDone)
            {
                if (round <= f)
                {
                    roundDone = false;
                    round++;
                    if (!FailStops)
                    {
                        List<TreeNode> leaves = tree.GetLeaves();
                        List<Node> nodes = new List<Node>();
                        nodes.Add(this); // Da this sonst nicht einbezogen würde.
                        foreach (Link l in Links) nodes.Add((Node)(l.SourceNode == this ? l.TargetNode : l.SourceNode));
                        nodes.Sort();
                        foreach (KeyValuePair<Node, List<TreeNode>> kvp in nodesThisRound)
                        {
                            foreach (TreeNode tn in kvp.Value)
                            {
                                TreeNode newNode = tn.Extend(kvp.Key.Id);
                                TreeNode tnt = tree.GetMatchingLeave(newNode);
                                tnt.Children.Add(newNode);
                            }
                        }
                        nodesThisRound.Clear();
                    }

                    if (round == f + 1)
                    {
                        isFinished = true;
                        decide(f);
                    }

                    if (!FailStops && RoundFinished != null) RoundFinished(this);
                }
                else
                {
                    throw new Exception("Der Algorithmus wurde bereits beendet.");
                }
            }
            else
            {
                throw new Exception("Die Runde wurde noch nicht abgehandelt.");
            }
        }

        /// <summary>
        /// Führt die Entscheidungsregel auf den Baum aus.
        /// </summary>
        /// <param name="f">Die maximal fehlerhaften Prozesse.</param>
        private void decide(int f)
        {
            if (!FailStops)
            {
                if (round == f + 1)
                {
                    isFinished = true;

                    Dictionary<int, int> resultCounter = new Dictionary<int, int>();
                    decide(resultCounter, tree);
                    decisionResultSet.Clear();

                    if (resultCounter.Count == 1)
                    {
                        foreach (KeyValuePair<int, int> kvp in resultCounter) decisionResultSet.Add(kvp.Key);
                        isConsensReached = true;
                    }
                    else
                    {
                        decisionResultSet.Add(Value);
                        isConsensReached = false;
                    }
                }

                // Prüfung ob der Algorithmus korrekt gewesen sein kann. Wurden genug Runden durchlaufen?
                int realFailed = this.FailStops ? 1 : 0; // wir gehen davon aus dass die Entscheidung NICHT atomar ist. (Ist sie aber.)
                foreach (Link l in Links) realFailed += ((Node)(l.SourceNode == this ? l.TargetNode : l.SourceNode)).FailStops ? 1 : 0;
                isDecisionCorrect = f >= realFailed;

                if (DecisionFinished != null) DecisionFinished(this);
            }
        }

        /// <summary>
        /// Zählt die Werte die im Baum enthalten sind.
        /// </summary>
        /// <param name="resultCounter">Die Zählliste.</param>
        /// <param name="treePart">Der aktuelle Baumknoten.</param>
        private void decide(Dictionary<int, int> resultCounter, TreeNode treePart)
        {
            if (!treePart.IsNull) resultCounter[treePart.Value] = resultCounter.ContainsKey(treePart.Value) ? resultCounter[treePart.Value] + 1 : 1;
            foreach (TreeNode tn in treePart.Children) decide(resultCounter, tn);
        }
        #endregion Methoden
    }
}
